package org.i0o.webplus.web.struts.action;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.actions.DispatchAction;
import org.i0o.webplus.annotation.Out;
import org.i0o.webplus.annotation.Read;
import org.i0o.webplus.commons.Scope;
import org.i0o.webplus.commons.util.ValidateUtil;
import org.i0o.webplus.commons.util.populator.ConvertUtils;
import org.i0o.webplus.web.base.action.WebAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author <a href="mailto:781131@gmail.com">HTF</a>
 */
public class BaseAction extends DispatchAction
	implements WebAction {
	protected final Logger log = LoggerFactory.getLogger(this.getClass());

	private HttpServletResponse response;

	private HttpServletRequest request;

	@Override
	public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception {
		this.request = request;
		this.response = response;

		String methodName = (String) (this.getAttribute("method") != null ? this.getAttribute("method") : null);

		// 如果没有指定所调用的方法，则使用默认方法
		if (ValidateUtil.isBlank(methodName)) {
			methodName = DEFAULT_METHOD_NAME;
		}

		Method method = this.getCallMethod(methodName);
		if (method == null) {
			this.getResponse().sendError(404);
			log.error("404:Page not found!");
			return null;
		}

		// 执行方法调用
		Object result = this.exec(method);

		if (result != null) {
			if (result instanceof ActionForward) {
				return (ActionForward) result;
			}
			return new ActionForward(result.toString());
		} else {
			return null;
		}
	}

	@Override
	public HttpServletRequest getRequest() {
		return this.request;
	}

	@Override
	public HttpServletResponse getResponse() {
		return this.response;
	}

	@Override
	public HttpSession getSession() {
		return this.request.getSession();
	}

	@Override
	public ServletContext getApplication() {
		return this.getSession().getServletContext();
	}

	@Override
	public void setAttribute(String key, Object value, Scope scope) {
		switch (scope) {
			case REQUEST:
				this.getRequest().setAttribute(key, value);
				break;
			case SESSION:
				this.getRequest().getSession().setAttribute(key, value);
				break;
			case APPLICATION:
				this.getRequest().getSession().getServletContext().setAttribute(key, value);
				break;
			default:
				this.setAttribute(key, value);
				break;
		}
	}

	@Override
	public void setAttribute(String key, Object value) {
		this.setAttribute(key, value, Scope.REQUEST);
	}

	@Override
	public Object getAttribute(String key, Scope scope) {
		switch (scope) {
			case REQUEST:
				return this.getRequest().getParameter(key);
			case SESSION:
				return this.getRequest().getSession().getAttribute(key);
			case APPLICATION:
				return this.getRequest().getSession().getServletContext().getAttribute(key);
			default:
				return this.getAttribute(key);
		}
	}

	@Override
	public Object getAttribute(String key) {
		Object value = this.getAttribute(key, Scope.REQUEST);
		if (value == null) {
			value = this.getAttribute(key, Scope.SESSION);
			if (value == null) {
				value = this.getAttribute(key, Scope.APPLICATION);
			}
		}
		return value;
	}

	@Override
	public void renderText(String msgStr) {
		this.getResponse().setContentType("text/plain; charset=UTF-8");
		this.render(msgStr);
	}

	@Override
	public void renderHTML(String msgStr) {
		this.getResponse().setContentType("text/html;charset=UTF-8");
		this.render(msgStr);
	}

	@Override
	public void renderXML(String msgStr) {
		this.getResponse().setContentType("text/xml;charset=UTF-8");
		this.render(msgStr);
	}

	@Override
	public void returnSuccess() {
		this.renderText(WebAction.SUCCESS);
	}

	@Override
	public void returnFailure() {
		this.renderText(WebAction.FAILURE);
	}

	private void render(String msgStr) {
		try {
			PrintWriter out = this.getResponse().getWriter();
			out.write(msgStr);
			out.close();
			this.getResponse().flushBuffer();
		} catch (IOException e) {
			log.error(e.getMessage());
		}
	}

	/**
	 * 获取执行方法
	 * 
	 * @param methodName 方法名称
	 * @return
	 */
	private Method getCallMethod(String methodName) {
		for (Class<?> clazz = this.getClass(); clazz != BaseAction.class; clazz = clazz.getSuperclass()) {
			Method[] tempMethods = clazz.getDeclaredMethods();
			for (Method tempMethod : tempMethods) {
				if (Modifier.PUBLIC == tempMethod.getModifiers() && tempMethod.getName().equals(methodName)) {
					return tempMethod;
				}
			}
		}
		return null;
	}

	/**
	 * 供内部使用的调用所指定方法的方法
	 * 
	 * @param method 所指定的需要被调用的方法
	 * @return 调用方法后所返回的结果
	 * @throws Exception
	 */
	private Object exec(Method method) throws Exception {
		// 获得所调用方法的参数类型和所使用的Annotation数组
		Class<?>[] type = method.getParameterTypes();
		Annotation[][] annotationArray = method.getParameterAnnotations();

		// 用于存放调用参数的对象数组
		Object[] paramTarget = new Object[type.length];

		// TODO 用于存放需要输出结果的Map
		// Map<String, Object> outParameterMap = new HashMap<String, Object>();

		// 构造调用所需要的参数数组
		for (int i = 0; i < type.length; i++) {
			Class<?> typeClasz = type[i];
			Annotation[] annotation = annotationArray[i];

			if (annotation == null || annotation.length == 0) {
				throw new Exception("Must specify a @Read annotation for every parameter in method: " + method.getName());
			}

			Read read = (Read) annotation[0];

			// //为了支持泛型
			// if (typeClasz == Object.class || typeClasz == IValueObject.class)
			// typeClasz = GenericsUtils.getGenericClass(clazz);
			// else if (typeClasz == Serializable.class)
			// typeClasz = GenericsUtils.getGenericClass(clazz, 1);
			// else if (typeClasz == Serializable[].class)
			// typeClasz =
			// Array.newInstance(GenericsUtils.getGenericClass(clazz, 1),
			// 0).getClass();
			//
			// //循环生成当前的调用参数
			paramTarget[i] = getParameter(typeClasz, read);

		}
		Object result = method.invoke(this, paramTarget);

		// 构造需要输出的结果
		for (int i = 0; i < type.length; i++) {
			Annotation[] annotation = annotationArray[i];
			if (annotation.length == 2) {
				Out out = (Out) annotation[1];
				if (out != null && paramTarget[i] != null) {
					this.setAttribute(out.key(), paramTarget[i], out.scope());
				}
			}
		}

		return result;
	}

	/**
	 * 设置参数
	 * 
	 * @param type
	 * @param read
	 * @return
	 */
	private Object getParameter(Class<?> type, Read read) {
		String key = read.key();

		if (key != null && key.length() > 0) {
			Object value = null;
			value = this.getAttribute(key, read.scope());

			if (read.defaultValue() != null && read.defaultValue().length() != 0 && value == null) {
				value = read.defaultValue();
			}
			// TODO 泛型还未处理
			try {

				return ConvertUtils.convert(value, type);
			} catch (RuntimeException e) {
				log.error("ClassCastException", e);
				return null;
			}
		} else {
			// 如果key为空则为bean

			Object obj;
			try {
				obj = type.newInstance();
			} catch (InstantiationException e) {
				log.debug("InstantiationException happened when initializing bussiness object", e);
				return null;
			} catch (IllegalAccessException e) {
				log.debug("IllegalAccessException happened when initializing bussiness", e);
				return null;
			}
			try {
				switch (read.scope()) {
					case REQUEST:
						ConvertUtils.populate(obj, this.getRequest().getParameterMap());
						break;
					case SESSION:
						// BeanUtils.populate(obj,
						// this.getRequest().getSession().get);
						break;
					case APPLICATION:
						// BeanUtils.populate(obj,
						// this.getRequest().getSession().getServletContext().getAttributeNames());
						break;
					default:
						ConvertUtils.populate(obj, this.getRequest().getParameterMap());
						break;
				}
			} catch (Exception e) {
				log.debug("IllegalAccessException happened when initializing bussiness", e);
				return null;
			}

			return obj;
		}
	}

}
